import { HttpException, HttpStatus, Injectable } from "@nestjs/common";
import axios, { AxiosRequestHeaders } from "axios";
import { Observable, catchError, concatMap, defer, forkJoin, map, of, tap } from "rxjs";
import * as yaml from "js-yaml"
import IHelper from "@/services/helper.service";
import ILogger from "@/services/logger.service";
import * as fs from "fs-extra"
import * as moment from "moment"
import * as path from "path"
import { error } from "console";
import { ConfigService } from "@nestjs/config";
import IResponse from "@/services/response.service";
import { RedisProvider } from "@/core/providers/redis.provider";
import * as shelljs from "shelljs"
import IGlobal, { ProxyGroup } from "@/services/global.service";

type ClashProxy = {
  "name": string,
  "type": string,
  "server": string
  "port": number
  "password": string
  "udp"?: boolean,
  "sni"?: string
  "cipher"?: string
  "skip-cert-verify"?: boolean
}

@Injectable()
export class ClashService {
  private readonly REDIS_PROXY_KEY: string = "WN_PROXY_SERVER_export_proxy"
  private readonly CLASH_MIXED_PORT: number = 6001
  private readonly CLASH_API_PORT: number = 6002
  private readonly CLASH_API_SECRET: string = "c4578b237b565459c5b5a02a5cd30657"

  parseYamlFromUri(uri: string): Observable<ClashProxy[]> {
    return defer(() => 
      axios.get(uri, {timeout: 5 * 1000})
    ).pipe(
      map(res => {
        let config: {proxies: ClashProxy[]} = yaml.load(res.data) as {proxies: ClashProxy[]}
        return config.proxies.map(e => {
          return {...e, name: IHelper.hash(32)}
        }) as ClashProxy[]
      }),
      catchError(() => of([])),
      tap((res) => {
        ILogger.info(`From ${uri} get ${res.length} proxies.`)
      })
    )
  }

  getYamlUriFromSub(): Observable<string[]> {
    return defer(() => 
      axios.get(`https://clashsub.com/free-subscribe`)
    ).pipe(
      map(res => {
        const html = res.data as string
        const reg = /<p>http(.+?)<\/p>/g
        const urlList = html.match(reg).map(e => e.replace("<p>", "").replace("</p>", "").trim())
        return urlList
      })
    )
  }

  getConfigPath(key: string): string {
    const targetPath = path.join(process.cwd(), `bin/config.d/config_${key}.yaml`)
    fs.ensureFileSync(targetPath)
    return targetPath
  }

  getIp() {
    return axios.get(`https://api.ipify.org/`, {
      proxy: {
        host: "127.0.0.1",
        port: 6001,
        protocol: "http"
      }
    }).then(res => res.data)
  }

  getAllProxies(group: ProxyGroup) {
    return defer(() => 
      axios.get(`http://127.0.0.1:${group.apiPort}/proxies`, {
        headers: this.getClashApiHeaders(group.secret)
      })
    ).pipe(
      map(d => d.data)
    )
  }

  checkAllProxies() {
    
  }

  checkDelay(proxyName: string, timeout?: number): Observable<{status: boolean, message: string, proxyName: string, data: any}> {
    return defer(() => 
      axios.get(`http://127.0.0.1:${this.CLASH_API_PORT}/proxies/${proxyName}/delay`, {
        params: {
          timeout: timeout || 5000,
          // url: "http://www.gstatic.com/generate_204",
          url: "https://www.bilibili.com",
        },
        headers: this.getClashApiHeaders()
      })
    ).pipe(
      map(res => {
        console.log(res.data)
        if (res?.data?.delay === undefined) {
          throw new HttpException(`Get delay failed.`, HttpStatus.BAD_REQUEST)
        }
        return {status: true, data: res.data, message: "success", proxyName}
      }),
      catchError(error => {
        return of({status: false, message: error?.response?.data?.message || error.message, data: null, proxyName})
      })
    )
  }

  switchProxy(proxyName: string): Observable<boolean> {
    return defer(() => 
      axios.put(`http://127.0.0.1:${this.CLASH_API_PORT}/proxies/Proxy`, {
        name: proxyName,
      }, {
        headers: {...this.getClashApiHeaders()}
      })
    ).pipe(
      map((res) => {
        console.log("rsss", res.data)
        return true
      }),
    )
  }

  startClashProcess() {
    const exampleConfigPath = this.getConfigPath("example")
    const content = ClashService.prepareClashConfig([], {port: this.CLASH_MIXED_PORT, apiPort: this.CLASH_API_PORT, secret: this.CLASH_API_SECRET})
    const yamlString: string = yaml.dump(content, {noRefs: true})
    fs.writeFileSync(exampleConfigPath, yamlString, "utf-8")
    const clashBinPath = path.join(process.cwd(), "bin/clash")
    const cmd: string = `${clashBinPath} -f ${exampleConfigPath}`
    console.log("ccccc", cmd)
    // /Users/eros/Documents/code/wonew/wonew-proxy-server/bin/clash -f /Users/eros/Documents/code/wonew/wonew-proxy-server/bin/config.d/config_example.yaml
    return new Promise<any>(resolve => {
      shelljs.exec(cmd, {silent: true}, function(code: number, stdout: string, stderr: string) {
        // console.log("code", code, stdout, stderr)
        resolve(code)
      })
    })
    
  }

  fetchConfig() {
    return this.getYamlUriFromSub().pipe(
      concatMap(uris => 
        forkJoin([...uris.map(uri => this.parseYamlFromUri(uri))])  
      ),
      map(res => {
        return res.reduce((acc, val) => {
          return [...acc, ...val]
        }, [])
      }),
      // /Users/eros/Documents/code/wonew/wonew-proxy-server/bin/clash -f /Users/eros/Documents/code/wonew/wonew-proxy-server/bin/config.d/config_m9DX7aXnhIBGCQPX3vyvHYfVcMRNHnUh.yaml
      // curl -x http://127.0.0.1:6001 http://www.qq.com
      map(list => {
        const validList = list.filter(e => {
          if (e.type === "wireguard") return false
          if (e.cipher !== "auto") return false
          return true
        })
        const content = ClashService.prepareClashConfig(validList, {port: this.CLASH_MIXED_PORT, apiPort: this.CLASH_API_PORT, secret: this.CLASH_API_SECRET})
        // const filepath = `/Users/eros/.config/clash/config-${moment().format("HH_mm_ss")}.yaml`
        const key: string = IHelper.hash(32)
        const filepath = this.getConfigPath(key)
        const debugpath = `/Users/eros/.config/clash/config_${key}.yaml`
        fs.ensureFileSync(debugpath)
        const yamlString: string = yaml.dump(content, {noRefs: true})
        fs.writeFileSync(filepath, yamlString, "utf-8")
        fs.writeFileSync(debugpath, yamlString, "utf-8")
        return filepath
      }),
      concatMap(configPath => 
        forkJoin(
          IGlobal.groups.map(e => this.reloadConfig(configPath, e))
        ).pipe(
          map(res => ({res, configPath}))
        )
        // this.reloadConfig(configPath).pipe(
        //   map(res => ({...res, configPath}))
        // )
      )
    )
  }

  getClashApiHeaders(secret?: string): AxiosRequestHeaders {
    return {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${secret || this.CLASH_API_SECRET}`
    } as AxiosRequestHeaders
  }

  reloadConfig(configPath: string, group?: ProxyGroup) {
    return defer(() => 
      axios.put(`http://127.0.0.1:${group?.apiPort || this.CLASH_API_PORT}/configs`, {
        path: configPath
      }, {
        params: {
          force: false,
        },
        headers: {...this.getClashApiHeaders(group?.secret)}
      })
    ).pipe(
      map(() => {
        return {status: true, message: "success"}
      }),
      catchError(error => {
        return of({status: false, message: `Reload config failed. ${error?.response?.data?.message || error.message}`})
        // throw new HttpException(`Reload config failed. ${error?.response?.data?.message || error.message}`, HttpStatus.BAD_REQUEST)
      })
    )
  }



  static getMockProxy(): ClashProxy[] {
    const proxy = {
      "name": "proxy000001",
      "type": "trojan",
      "server": "iplc-hk-beta1.trojanwheel.com",
      "port": 4003,
      "password": "ysQ83fsyXBteE5eWVA",
      "alpn": [
        "h2",
        "http/1.1"
      ],
      "skip-cert-verify": true
    } as ClashProxy
    return [proxy]
  }


  static prepareClashConfig(_proxies: ClashProxy[], options?: {port?: number, apiPort?: number, secret?: string}): Object {
    const proxies = _proxies.length <= 0 ? ClashService.getMockProxy() : [..._proxies]
    let examplePath = path.join(process.cwd(), "example.yaml")
    const exampleConfig: any = yaml.load(fs.readFileSync(examplePath, "utf-8"))
    exampleConfig["mixed-port"] = options?.port || 6001
    exampleConfig["external-controller"] = `127.0.0.1:${options?.apiPort || 6002}`
    exampleConfig["secret"] = options?.secret || "azc2698*#@"
    exampleConfig.proxies = proxies
    exampleConfig["proxy-groups"][0].proxies = proxies.map(e => e.name)
    exampleConfig["proxy-groups"][1].proxies = ["Auto", ...(proxies.map(e => e.name))]
    return exampleConfig
  }
}
